package aspect;


import Utils.NetworkUtil;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.CodeSignature;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;

@Slf4j
@Aspect
@Component
public class RequestAspect {

    private final List ingoreUrl = new ArrayList() {{
        add("/api/v1/log");
        add("/error");
    }};

    @Around("@within(org.springframework.stereotype.Controller) || @within(org.springframework.web.bind.annotation.RestController)")
    public Object doAround(JoinPoint joinPoint) throws Throwable {
        String url = null;
        Map data = null;
        // log traceId
        String traceId = MDC.get("traceId");
        Long starTime = System.currentTimeMillis();
        String beginTime = null;
        try {
            beginTime = DateUtil.now();
            HttpServletRequest request = getRequest();
            url = request.getRequestURI();
            if (!ingoreUrl.contains(url)) {
                data = getContentParams(joinPoint);
                log.info(" HTTP_REQUEST_INFO:{}", JSONObject.toJSONString(requestLog(data, traceId, beginTime, request)));

            }

        } catch (Exception e) {
            log.error("LogServiceAspect---error", e);
        }
        Object result = null;
        try {
            result = ((ProceedingJoinPoint) joinPoint).proceed();
        } catch (Exception e) {
            log(url, data, traceId, starTime, beginTime, e);
            throw e;
        }
        log(url, data, traceId, starTime, beginTime, result);
        return result;

    }

    private void log(String url, Map data, String traceId, Long starTime, String beginTime, Object result) {
        try {
            if (!ingoreUrl.contains(url)) {
                responseLog(data, traceId, beginTime, starTime, result);
            }
        } catch (Exception e) {
            log.error("LogResponseResultAspect---error", e);
        }
    }


    private void responseLog(Map data, String traceId, String beginTime, Long starTime, Object result) throws IOException {
        HttpServletRequest request = getRequest();

        LinkedHashMap logMap = requestLog(data, traceId, beginTime, request);

        Long consumeTime = (System.currentTimeMillis() - starTime);
        String consumeLevel = getConsumeLevel(consumeTime);
        logMap.put("consumeTime", consumeTime);
        logMap.put("consumeLevel", consumeLevel);
        logMap.put("isOk", result instanceof Exception ? 0 : 1);
        logMap.put("response", getDataResult(result));
        log.info(" HTTP_RESPONSE_INFO:{}", JSONObject.toJSONString(logMap, SerializerFeature.DisableCircularReferenceDetect));
    }


    private LinkedHashMap requestLog(Map data, String traceId, String beginTime, HttpServletRequest request) throws IOException {
        LinkedHashMap logMap = new LinkedHashMap(16);
        logMap.put("reqTime", beginTime);
        logMap.put("traceId", traceId);
        logMap.put("uri", request.getRequestURI());
        logMap.put("method", request.getMethod());
        logMap.put("ip", NetworkUtil.getIpAddress(request));
        logMap.put("header", getHeader(request));
        logMap.put("request", data);
        //TODO
        logMap.put("UserContext", "xxxxx");
        return logMap;
    }


    private Map getHeader(HttpServletRequest request) {
        //获取请求头的所有name值
        Enumeration er = request.getHeaderNames();
        Map header = new LinkedHashMap(8);

        while(er.hasMoreElements()) {
            String name = (String) er.nextElement();
            String value = request.getHeader(name);
            header.put(name, value);

        }
        return header;
    }


    private String getConsumeLevel(Long consumeTime) {
        String consumeLevel = "";
        if (consumeTime <= 200L) {
            consumeLevel = "MS200";
        } else if (500L >= consumeTime && consumeTime > 200L) {
            consumeLevel = "MS500";
        } else if (500L < consumeTime && consumeTime < 1000L) {
            consumeLevel = "MS1000";
        } else if (1000L <= consumeTime && consumeTime < (60 * 1000L)) {
            consumeLevel = "S";
        } else if ((60 * 1000L) <= consumeTime) {
            consumeLevel = "M";
        }
        return consumeLevel;
    }

    private Object getDataResult(Object object) {
        if (object instanceof File) {
            return ((File) object).getName();
        }
        if (object instanceof MultipartFile) {
            return ((MultipartFile) object).getOriginalFilename();
        }
        if (object instanceof OutputStream) {
            return "stream";
        }
        //这里的异常非完全暴露用户，只是为方便排查问题或者异常的日志分析
        if (object instanceof Exception) {
            Map<String, Object> map = new HashMap(4);
            map.put("exceptionType", object.getClass().getSimpleName());
            map.put("msg", ((Exception) object).getMessage());
            return map;
        }
        return object;

    }


    private Map<String, Object> getContentParams(JoinPoint joinPoint) {
        String[] params = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
        Object[] values = joinPoint.getArgs();
        HashMap<String, Object> contentParams = new HashMap<>(params.length);
        for (int i = 0; i < params.length; i++) {
            if (values[i] instanceof HttpServletRequest || values[i] instanceof HttpServletResponse) {
                break;
            }
            contentParams.put(params[i], values[i]);
            if (values[i] instanceof MultipartFile) {
                contentParams.put("file", ((MultipartFile) values[i]).getOriginalFilename());
            }
        }
        return contentParams;
    }

    public HttpServletRequest getRequest() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        return requestAttributes == null ? null : ((ServletRequestAttributes) requestAttributes).getRequest();
    }

}

